//
//  off-screen-rendered.swift
//  iPhone
//
//  Created by Toj on 6/19/23.
//

// MARK: - 1. 什么叫离屏渲染?
/** 离屏渲染: Off-Screen Rendering 指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作.
 * 如果要在显示屏上显示内容, 我们至少需要一块与屏幕像素数据量一样大的frame buffer, 作为像素数据存储区域, 而这也是GPU存储渲染结果的地方. 如果有时因为面临一些限制, 无法把渲染结果直接写入frame buffer, 而是先暂存在另外的内存区域, 之后再写入frame buffer, 那么这个过程被称之为离屏渲染.
 *
 * 简单点就是GPU 渲染某些图层后不能达到最终效果, 需要临时开辟出一个新的缓冲区先去渲染一些图层, 再去渲染特殊图层, 最终合并成1个图层, 再到frame buffer通过视频控制器渲染出来. 那个临时开辟的渲染操作就叫离屏渲染
 *
 * NOTE: drawRect 其实通过CPU渲染就是俗称的“软件渲染”, 而真正的离屏渲染发生在GPU.
 * 比如绘制文字, 图形以及ImageIO的图片解码core Graphics等等,这些必须由CPU完成,然后再把数据传给GPU.
 */

// MARK: - 2. 常见的离屏渲染
/*
 * 规律: 多个图层都得需要裁剪, 最后合成就会触发离屏渲染. 单个图层就不会触发
 * 1. 圆角 + 其他图层
 * 原因就如同上面提到的, 不得已只能另开一块内存来操作.
 * 而如果只是设置cornerRadius(如不需要剪切内容, 只需要一个带圆角的边框),
 * 或者只是需要裁掉矩形区域以外的内容(虽然也是剪切, 但是稍微想一下就可以发现, 对于纯矩形而言,
 * 实现这个算法似乎并不需要另开内存), 并不会触发离屏渲染.
 * 关于剪切圆角的性能优化, 根据场景不同有几个方案可供选择.
 *
 * 2. 阴影 shadow
 * 其原因在于, 虽然layer本身是一块矩形区域, 但是阴影默认是作用在其中”非透明区域“的,
 * 而且需要显示在所有layer内容的下方, 因此根据画家算法必须被渲染在先.
 * 但矛盾在于此时阴影的本体（layer 和 子layer）都还没有被组合到一起,
 * 怎么可能在第一步就画出只有完成最后一步之后才能知道的形状呢?
 * 不过如果我们能够预先告诉CoreAnimation(通过shadowPath属性) 阴影的几何形状, 那么阴影当然可以先被独立渲染出来, 不需要依赖layer本体, 也就不再需要离屏渲染了.
 
 * 3. 光栅化: 不复用改动不大的视图不建议开启
 * view.layer.shouldRasterize = true
 * 光栅化是一种缓存机制, 会额外开启一个缓冲区去渲染某个位图, 并且CALayer 的渲染位图结果 bitmap 保存下来 这个位图,之后如果能够重用,就可以直接发送到frame buffer.
 * 光栅化开启后会缓存这个图片的bitmap, 如果对应的layer及其sublayers没有发生改变, 下一次要显示这张图的时候, CPU会直接从缓存中获取bitmap, 交给GPU进行显示, GPU就不需要去渲染图层了, 从而能提高性能.
 * 因此, 光栅化的主要应用场景在于layer不会变且图片是静态的情况下, 才会有优化作用.
 * 但是，光栅化仅能缓存100毫秒，而且只能存储屏幕大小2.5倍的数据，缓存空间十分有限，慎用.
 * 4. 遮罩模糊图层:
 * mask也是layer类型, 但是是覆盖在layer和其所有子layer组合之上, 根据渲染原理.
 * 屏幕上的每一个像素点都是当前像素点上多个layer通过GPU混合颜色计算出来, 因此多加一个mask会增加GPU计算难度, 导致无法一次性渲染缓存到frame buffer里面,
 * 因此会触发离屏渲染
 * 5. 抗锯齿会触发离屏渲染:
 * 当view缩放或者旋转会造成边框出现锯齿, 在出现锯齿是时, 打开allowsEdgeAntialiasing = YES时, 会将边框磨平, 而锯齿的计算也会触发离屏渲染.
 * 当contentMode 是 ScaleToFill 的时候, 是不会触发离屏渲染的.
 * 因此在实际开发中要按实际的控件大小或者同比例来进行切图.
 
 * 6. 透明组: allowsGroupOpcity = true
 * 当视图没有子视图时, 设置alpha<1, 不会触发离屏渲染,
 * 当有子视图时, 因为GPU渲染时, 除了要渲染子视图, 还需要渲染父视图的内容, 没有办法一步到位, 所以会触发.
 * 7. 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
 */

// MARK: - 3. 如何尽量避免离屏渲染
/** 避免方法:
 * 1. 圆角: 直接使用带圆角的图片, 或者替换背景色为带圆角的纯色背景图, 从而避免图层裁剪圆角合并.
 ** 如果子视图能单独处理, 就单独处理, 分别对层进行切圆角,同时背景能clear的就clear
 * 2. mask: 再增加一个和背景色相同的遮罩 mask 覆盖在最上层, 盖住四个角, 营造出圆角的形状.
 ** 但这种方式难以解决背景色为图片或渐变色的情况.
 * 3. UIBezierPath 用贝塞尔曲线绘制闭合带圆角的矩形, 在上下文中设置只有内部可见,
 ** 再将不带圆角的 layer 渲染成图片, 添加到贝塞尔矩形中.
 ** 这种方法效率更高, 但是 layer 的布局一旦改变, 贝塞尔曲线都需要手动地重新绘制,
 ** 所以需要对 frame, color 等进行手动地监听并重绘.
 * 4. CoreGraphics重写 drawRect:用 CoreGraphics 相关方法, 在需要应用圆角时进行手动绘制.
 ** 不过 CoreGraphics 效率也很有限, 如果需要多次调用也会有效率问题.
 * 5. 处理阴影: 使用shadowPath可以避免离屏渲染
 * shadowPath会预先定义好阴影的位置, 先去画shadowPath层, 在去画其他图层, 避免层次合并
 * 6. 光栅化的时候需要注意以下几点:
 * a. 如果 layer 不能被复用，则没有必要打开光栅化
 * b. 如果 layer 不是静态, 需要被频繁修改, 比如处于动画之中, 那么开启离屏渲染反而影响效率
 * c. 离屏渲染缓存内容有时间限制, 缓存内容 100ms 内如果没有被使用, 那么就会被丢弃, 无法进行复用
 * d. 离屏渲染缓存空间有限, 超过 2.5 倍屏幕像素大小的话也会失效, 无法复用
 */
